/*
 * maz_app_debugcmd.c
 *
 *  Created on: 2020年6月14日
 *      Author: Paul
 */

#include "maz_app.h"
#include "maz_com.h"
#include "maz_drv.h"
#include "maz_mod.h"
#include "maz_cpnt_debugcmd.h"

/**
 * "dlvl" variables must be defined if debug message is to be printed through the dlog/dmsg, and "dlvl" must be declared static
 */
static int dlvl = DINF;

MAZAPP_DEBUGCMD_CTRL g_mazapp_debugcmd;

osThreadId_t debugcmd_taskHandle;
const osThreadAttr_t debugcmd_task_attributes =
{
    .name = "debugcmd_task",
    .priority = (osPriority_t) osPriorityHigh,
    .stack_size = 128 * 8,                              // 栈空间太小会导致系统崩溃, 原因待分析
};

/* 内部函数接口 */
int MAZ_App_debugcmd_prompt(void);
int MAZ_App_debugcmd_rigister(void);

/**
 * @brief  调试命令组件预初始化
 * @retval 错误码
 */
int MAZ_App_debugcmd_preinit(void)
{
    return MAZRET_OK;
}

/**
 * @brief  调试命令组件初始化
 * @retval 错误码
 */
int MAZ_App_debugcmd_init(void)
{
    MAZAPP_DEBUGCMD_CTRL *ctrl;

    ctrl = &g_mazapp_debugcmd;
    ctrl->priv.r = 0;
    ctrl->priv.w = 0;
    memset(ctrl->priv.buf, 0, MAZAPP_DEBUGCMD_TOTAL_LEN);
    ctrl->priv.base = 0;
    ctrl->priv.offset = 0;

    MAZ_App_debugcmd_rigister();

    debugcmd_taskHandle = osThreadNew(MAZ_App_debugcmd_task, NULL, &debugcmd_task_attributes);
    MAZASSERT_RETVAL(NULL == debugcmd_taskHandle, MAZRET_ENULLP, "err: osThreadNew MAZ_App_debugcmd_task");

    return MAZRET_OK;
}

/**
 * @brief  调试命令组件处理任务
 */
void MAZ_App_debugcmd_task(void *argument)
{
    int ret = 0;
    char cmd[MAZCPNT_CMD_MAX_STR_LEN];              // 用户缓存命令的临时字符数组
    uint32_t fill = 0;                              // 串口接收队列中有效数据长度
    uint32_t length = 0;                            // 命令长度
    MAZAPP_DEBUGCMD_CTRL *ctrl;
    char vt100[4] = { 0 };                          // 暂存解析到的VT100控制码的值
    char match_cmd[MAZCPNT_CMD_MAX_STR_LEN];
    int match_length = 0;
    int success_count = 0;                          // 命令执行成功的次数计数

    ctrl = &g_mazapp_debugcmd;
    memset(cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);
    memset(match_cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);
    MAZ_App_debugcmd_prompt();

    while (1)
    {
        fill = MAZAPP_DEBUGCMD_FILL_LEN(ctrl->priv.r, ctrl->priv.w, MAZAPP_DEBUGCMD_TOTAL_LEN);
        while (fill > 0)
        {
            /* 通过回车和换行将串口接收到的数据分割为一条条命令 */
            cmd[length] = ctrl->priv.buf[ctrl->priv.r];
            ctrl->priv.r++;
            if (ctrl->priv.r >= MAZAPP_DEBUGCMD_TOTAL_LEN)
            {
                ctrl->priv.r = 0;
            }

            /* 字符回显, 针对没有输入情况下按退格键和TAB键做特殊处理 */
            if (length == 0 && (cmd[length] == '\b' || cmd[length] == '\t'))
            {
                cmd[length] = '\0';
                length = 0;
                __io_putchar('\a');
                break;
            }
            else if (cmd[length] == '\t')                    // TAB 自动补全逻辑
            {
                cmd[length] = '\0';
                ret = MAZ_CPNT_debugcmd_automatic_completion(cmd, match_cmd, &match_length);
                if (MAZRET_ENEWLINE == ret)
                {
                    /* 打印新行 */
                    MAZ_App_debugcmd_prompt();

                    if (match_length)
                    {
                        strcat(cmd, match_cmd);
                        length += match_length;
                    }

                    printf("%s", cmd);
                    fflush(stdout);
                }
                else if (MAZRET_EAUTO == ret)
                {
                    /* 只有一个命令包含, 则自动补全这个命令 */
                    if (match_length)
                    {
                        strcat(cmd, match_cmd);
                        length += match_length;
                        printf("%s", match_cmd);
                        fflush(stdout);
                    }
                }
                else if (MAZRET_ERING == ret)
                {
                    /* 命令输入错误, 输出提示音 */
                    printf("\a");
                    fflush(stdout);
                }

                break;
            }
            else if (cmd[length] == '\033')    // VT100 '\033'
            {
                /* 发现是VT100控制码, 则继续取出后面的两个字符 */
                fill = MAZAPP_DEBUGCMD_FILL_LEN(ctrl->priv.r, ctrl->priv.w, MAZAPP_DEBUGCMD_TOTAL_LEN);
                while (fill < 2)
                {
                    osDelay(10);
                    fill = MAZAPP_DEBUGCMD_FILL_LEN(ctrl->priv.r, ctrl->priv.w, MAZAPP_DEBUGCMD_TOTAL_LEN);
                }

                vt100[0] = ctrl->priv.buf[ctrl->priv.r];
                ctrl->priv.r++;
                if (ctrl->priv.r >= MAZAPP_DEBUGCMD_TOTAL_LEN)
                {
                    ctrl->priv.r = 0;
                }

                vt100[1] = ctrl->priv.buf[ctrl->priv.r];
                ctrl->priv.r++;
                if (ctrl->priv.r >= MAZAPP_DEBUGCMD_TOTAL_LEN)
                {
                    ctrl->priv.r = 0;
                }

                if (vt100[0] == '[')
                {
                    /* 判断是否是上下键 */
                    if (vt100[1] == 'A' || vt100[1] == 'B')
                    {
                        int i = 0;
                        int index = 0;

                        if ((vt100[1] == 'A') && (ctrl->priv.offset < (MAZAPP_DEBUGCMD_HISTORY_COUNT - 1))
                                && (ctrl->priv.offset < success_count))
                        {
                            /* 从最新输入行切换到历史命令时, 暂存目前的输入内容*/
                            if (0 == ctrl->priv.offset)
                            {
                                /* 将最后的 0x1b 换码符清掉, 否则长度对应不上 */
                                cmd[length] = '\0';
                                memset(ctrl->priv.history[ctrl->priv.base], 0, MAZCPNT_CMD_MAX_STR_LEN);
                                memcpy(ctrl->priv.history[ctrl->priv.base], cmd, strlen(cmd));
                            }
                            ctrl->priv.offset++;

                            index = ctrl->priv.base - ctrl->priv.offset;
                            if (index < 0)
                            {
                                index = index + MAZAPP_DEBUGCMD_HISTORY_COUNT;
                            }

                            for (i = 0; i < length; i++)
                            {
                                printf("\b \b");
                                fflush(stdout);
                            }
                            memset(cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);

                            length = strlen((char*) ctrl->priv.history[index]);
                            memcpy(cmd, ctrl->priv.history[index], length);

                            printf("\r");
                            MAZ_App_debugcmd_prompt();
                            printf("%s", cmd);
                            fflush(stdout);
                        }
                        else if ((vt100[1] == 'B') && (ctrl->priv.offset > 0))
                        {
                            ctrl->priv.offset--;

                            index = ctrl->priv.base - ctrl->priv.offset;
                            if (index < 0)
                            {
                                index = index + MAZAPP_DEBUGCMD_HISTORY_COUNT;
                            }

                            for (i = 0; i < length; i++)
                            {
                                printf("\b \b");
                                fflush(stdout);
                            }
                            memset(cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);

                            length = strlen((char*) ctrl->priv.history[index]);
                            memcpy(cmd, ctrl->priv.history[index], length);

                            printf("\r");
                            MAZ_App_debugcmd_prompt();
                            printf("%s", cmd);
                            fflush(stdout);
                        }
                        else
                        {
                            /* 已经切换到了记录的最早的历史命令了, 输出提示音 */
                            printf("\a");
                            fflush(stdout);
                        }
                    }
                    else if (vt100[1] == '3') /* 判断是否是佩锐 512 退格键 */
                    {
                        fill = MAZAPP_DEBUGCMD_FILL_LEN(ctrl->priv.r, ctrl->priv.w, MAZAPP_DEBUGCMD_TOTAL_LEN);
                        while (fill < 1)
                        {
                            osDelay(10);
                            fill = MAZAPP_DEBUGCMD_FILL_LEN(ctrl->priv.r, ctrl->priv.w, MAZAPP_DEBUGCMD_TOTAL_LEN);
                        }

                        vt100[2] = ctrl->priv.buf[ctrl->priv.r];
                        ctrl->priv.r++;
                        if (ctrl->priv.r >= MAZAPP_DEBUGCMD_TOTAL_LEN)
                        {
                            ctrl->priv.r = 0;
                        }

                        if (vt100[2] == '~')
                        {
                            if (length == 0)
                            {
                                cmd[length] = '\0';
                                length = 0;
                                __io_putchar('\a');
                                break;
                            }

                            __io_putchar('\b');
                            __io_putchar(' ');
                            __io_putchar('\b');
                            cmd[length] = '\0';
                            if (length > 0)
                            {
                                length--;
                            }
                            break;
                        }
                    }
                }

                cmd[length] = '\0';
                break;
            }
            else
            {
                __io_putchar(cmd[length]);
            }

            /* 适配 XShell 串口终端 */
            if (cmd[length] == '\r')
            {
                __io_putchar('\n');
            }

            /* 回车换行键处理 */
            if (cmd[length] == '\r' || cmd[length] == '\n')
            {
                cmd[length] = '\0';
                ret = MAZ_CPNT_debugcmd_execute(cmd);
                if (MAZRET_OK == ret)
                {
                    success_count++;
                    /* 命令执行成功后, 将命令记录到 history 命令列表 */
                    memset(ctrl->priv.history[ctrl->priv.base], 0, MAZCPNT_CMD_MAX_STR_LEN);
                    memcpy(ctrl->priv.history[ctrl->priv.base], cmd, strlen(cmd));
                    MAZAPP_DEBUGCMD_HISTORY_NEXT(ctrl->priv.base);
                    ctrl->priv.offset = 0;
                }
                MAZ_App_debugcmd_prompt();
                memset(cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);
                length = 0;
                break;
            }

            if (cmd[length] == '\b') /* 退格键处理 */
            {
                __io_putchar(' ');
                __io_putchar('\b');
                cmd[length] = '\0';
                if (length > 0)
                {
                    length--;
                }
            }
            else if (cmd[length] == 0x03) /* CTRL + C 终止信号处理，放弃本次输入 */
            {
                dmsg(DERR, "^C\r\n");
                MAZ_App_debugcmd_prompt();
                length = 0;
                break;
            }
            else
            {
                length++;
            }

            /* 输入命令长度超过 DEBUGCMD 组件约束 */
            if (length >= MAZCPNT_CMD_MAX_STR_LEN)
            {
                memset(cmd, 0, MAZCPNT_CMD_MAX_STR_LEN);
                dmsg(DERR, "lenght over, length = %lu\r\n", length);
                length = 0;
            }

            /* 判断是否退出循环 */
            fill = MAZAPP_DEBUGCMD_FILL_LEN(ctrl->priv.r, ctrl->priv.w, MAZAPP_DEBUGCMD_TOTAL_LEN);
        }
        osDelay(20);
    }
}

/**
 * @brief 打印命令提示符
 * @retval 错误码
 */
int MAZ_App_debugcmd_prompt(void)
{
    printf(">>> ");
    fflush(stdout);
    return MAZRET_OK;
}

/**
 * @brief  打印队列信息
 * @retval 错误码
 */
int MAZ_DBGCMD_debugcmd_dump_queue(void *cmd, char *param)
{
    uint32_t idle = 0;
    uint32_t fill = 0;
    MAZAPP_DEBUGCMD_CTRL *ctrl;

    ctrl = &g_mazapp_debugcmd;
    fill = MAZAPP_DEBUGCMD_FILL_LEN(ctrl->priv.r, ctrl->priv.w, MAZAPP_DEBUGCMD_TOTAL_LEN);
    idle = MAZAPP_DEBUGCMD_IDLE_LEN(ctrl->priv.r, ctrl->priv.w, MAZAPP_DEBUGCMD_TOTAL_LEN);

    dmsg(DINF, "total/r/w/fill/idle = %d/%d/%d/%lu/%lu\r\n", MAZAPP_DEBUGCMD_TOTAL_LEN, ctrl->priv.r, ctrl->priv.w,
         fill, idle);

    return MAZRET_OK;
}

/**
 * @brief  打印串口接收缓冲池数据值
 * @retval 错误码
 */
int MAZ_DBGCMD_debugcmd_dump_buff(void *cmd, char *param)
{
    uint8_t i = 0;
    uint8_t j = 0;
    uint8_t col = 16;                                   // 每行显示多少列数据
    uint8_t row = MAZAPP_DEBUGCMD_TOTAL_LEN / col;      // 总共显示多少行
    uint8_t ch = 0;
    MAZAPP_DEBUGCMD_CTRL *ctrl;

    ctrl = &g_mazapp_debugcmd;

    for (i = 0; i < row; i++)
    {
        printf("%08x  ", i * col);
        for (j = 0; j < col; j++)
        {
            printf("%02x ", ctrl->priv.buf[i * col + j]);
            if (j % 8 == 7)
                printf(" ");
        }

        printf("|");
        for (j = 0; j < col; j++)
        {
            ch = ctrl->priv.buf[i * col + j];

            if ('\t' == ch)
                ch = '>';
            else if ('\b' == ch)
                ch = '<';
            else if ('\r' == ch)
                ch = '\\';
            else if ('\n' == ch)
                ch = '\\';
            else if (0x03 == ch)
                ch = '^';
            else if (ch < 32 || ch >= 127)
                ch = '.';

            printf("%c", ch);
        }
        printf("|\r\n");
    }

    return MAZRET_OK;
}

/**
 * @brief  清屏命令回调函数
 * @retval 错误码
 */
int MAZ_DBGCMD_clear(void *cmd, char *param)
{
    /* 基于 VT100 控制码实现清屏动作 */
    __io_putchar('\x1b');
    __io_putchar('[');
    __io_putchar('2');
    __io_putchar('J');
    __io_putchar('\x1b');
    __io_putchar('[');
    __io_putchar('H');
    return MAZRET_OK;
}

/**
 * @brief  系统重启回调函数
 * @retval 错误码
 */
int MAZ_DBGCMD_reboot(void *cmd, char *param)
{
    /* 关闭总中断 */
    __set_FAULTMASK(1);

    /* 重启系统 */
    NVIC_SystemReset();

    return MAZRET_OK;
}

static MAZCPNT_MAIN_CMD mcmd_dcmd           = { "dcmd",         "debugcmd cpnt debug command." };
static MAZCPNT_SUB_CMD  scmd_dcmd_dumpbuff  = { "dumpbuff",     "dcmd dumpbuff.",       MAZ_DBGCMD_debugcmd_dump_buff };
static MAZCPNT_SUB_CMD  scmd_dcmd_dumpqueue = { "dumpqueue",    "dcmd dumpqueue.",      MAZ_DBGCMD_debugcmd_dump_queue };

static MAZCPNT_MAIN_CMD mcmd_clear          = { "clear",        "clear screen.",        MAZ_DBGCMD_clear};
static MAZCPNT_MAIN_CMD mcmd_reboot         = { "reboot",       "reset system.",        MAZ_DBGCMD_reboot};

/**
 * @brief  系统以及调试命令组件本身相关的调试命令注册
 * @retval 错误码
 */
int MAZ_App_debugcmd_rigister(void)
{
    MAZ_CPNT_debugcmd_mcmd_register(&mcmd_dcmd);
    MAZ_CPNT_debugcmd_scmd_register(&mcmd_dcmd, &scmd_dcmd_dumpqueue);
    MAZ_CPNT_debugcmd_scmd_register(&mcmd_dcmd, &scmd_dcmd_dumpbuff);

    MAZ_CPNT_debugcmd_mcmd_register(&mcmd_clear);
    MAZ_CPNT_debugcmd_mcmd_register(&mcmd_reboot);
    return MAZRET_OK;
}
